﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;

namespace Aphysoft.Vate.Graphics
{
    public class MouseTrackingCameraController : CameraController
    {
        #region Properties
        /// <summary>
        /// Working camera.
        /// </summary>
        private Camera workingCamera = null;
        /// <summary>
        /// Mouse track horizontal positive limit.
        /// </summary>
        private float horizontalPositiveTrackLimit = 0.95f;
        /// <summary>
        /// Gets or sets horizontal positive limit when tracking with mouse.
        /// </summary>
        public float HorizontalPositiveTrackLimit
        {
            get { return horizontalPositiveTrackLimit; }
            set { horizontalPositiveTrackLimit = MathHelper.Clamp(value, 0, 0.9999f); }
        }
        /// <summary>
        /// Mouse track horizontal negative limit.
        /// </summary>
        private float horizontalNegativeTrackLimit = 0.95f;
        /// <summary>
        /// Gets or sets horizontal negative limit when tracking with mouse.
        /// </summary>
        public float HorizontalNegativeTrackLimit
        {
            get { return horizontalNegativeTrackLimit; }
            set { horizontalNegativeTrackLimit = MathHelper.Clamp(value, 0, 0.9999f); }
        }
        /// <summary>
        /// Gets or sets horizontal limit when tracking with mouse. Affects both positive and negative limit.
        /// </summary>
        public float HorizontalTrackLimit
        {
            get { return HorizontalPositiveTrackLimit; }
            set
            {
                HorizontalPositiveTrackLimit = value;
                HorizontalNegativeTrackLimit = value;
            }
        }
        /// <summary>
        /// Invert horizontal tracking.
        /// </summary>
        private bool invertHorizontal = false;
        /// <summary>
        /// Gets or sets a value indicating whether camera horizontal tracking.
        /// </summary>
        public bool InvertHorizontal
        {
            get { return invertHorizontal; }
            set { invertHorizontal = value; }
        }
        /// <summary>
        /// Invert vertical tracking
        /// </summary>
        private bool invertVertical = false;
        /// <summary>
        /// Gets or sets a value indicating whether camera vertical tracking.
        /// </summary>
        public bool InvertVertical
        {
            get { return invertVertical; }
            set { invertVertical = value; }
        }
        /// <summary>
        /// Mouse track speed limit.
        /// </summary>
        private float speedLimit = 10.0f;
        /// <summary>
        /// Gets or sets speed limit of mouse tracking.
        /// </summary>
        public float SpeedLimit
        {
            get { return speedLimit; }
            set { speedLimit = value; }
        }
        #endregion

        #region Constructor
        public MouseTrackingCameraController()
        {

        }
        #endregion

        #region Methods

        public void Reset()
        {
            Vate.SetMousePosition(
                new Vector2(
                    ProjectorManager.DefaultProjector.HalfWidth,
                    ProjectorManager.DefaultProjector.HalfHeight));            
        }

        /// <summary>
        /// Mouse tracks camera target.
        /// </summary>
        public void Track()
        {
            Track(0.005f);
        }

        /// <summary>
        /// Mouse tracks camera target by specified amount. 
        /// </summary>
        /// <param name="amount">Tracks amount.</param>
        public void Track(float amount)
        {
            Vector2 mousePosition = Vate.GetMousePosition();
                            
            float anglesX = 0, anglesY = 0;

            if (mousePosition.X != ProjectorManager.DefaultProjector.HalfWidth)
                anglesX -= (mousePosition.X - ProjectorManager.DefaultProjector.HalfWidth);
            if (mousePosition.Y != ProjectorManager.DefaultProjector.HalfHeight)
                anglesY -= (mousePosition.Y - ProjectorManager.DefaultProjector.HalfHeight);

            if (Camera.DefineCameraTarget == false) // camera target = camera forward + camera position
            {
                if (workingCamera != Camera)
                {
                    if (Camera.Up != Camera.CameraUp)
                        Camera.Rotate(Camera.Up, Camera.CameraUp);

                    workingCamera = Camera;
                }

                anglesX = amount * MathHelper.Clamp(anglesX, -speedLimit, speedLimit);
                anglesY = amount * MathHelper.Clamp(anglesY, -speedLimit, speedLimit);

                // vertical rotation
                Camera.Rotate(Camera.Right, invertVertical ? -anglesY : anglesY);

                // limit vertical rotation , never happen if CameraUp = Up
                if (Camera.CosineAngleBetweenCameraUpAndCameraForward > horizontalPositiveTrackLimit)
                {
                    // rotate to normal
                    Camera.Rotate(Camera.Forward, Camera.CameraNormalForward);

                    // rotate to 0.95f
                    float p2 = Vector3.Divide(Camera.CameraUp, horizontalPositiveTrackLimit).LengthSquared();
                    float y2 = Camera.CameraUp.LengthSquared();
                    float xLength = (float)Math.Sqrt(p2 - y2);

                    Camera.Rotate(Camera.Forward, Vector3.Normalize(Vector3.Add(Camera.CameraUp, Vector3.Multiply(Camera.Forward, xLength))));
                }
                else if (Camera.CosineAngleBetweenCameraUpAndCameraForward < -horizontalNegativeTrackLimit)
                {
                    Camera.Rotate(Camera.Forward, Vector3.Cross(Camera.CameraUp, Camera.Right));

                    // rotate to 0.95f
                    float p2 = Vector3.Divide(Camera.CameraUp, -horizontalNegativeTrackLimit).LengthSquared();
                    float y2 = Camera.CameraUp.LengthSquared();
                    float xLength = (float)Math.Sqrt(p2 - y2);

                    Camera.Rotate(Camera.Forward, Vector3.Normalize(Vector3.Add(Vector3.Negate(Camera.CameraUp), Vector3.Multiply(Camera.Forward, xLength))));
                }

                // horizontal rotation
                Camera.Rotate(Camera.CameraUp, invertHorizontal ? -anglesX : anglesX);

                Vate.SetMousePosition(
                    new Vector2(
                        ProjectorManager.DefaultProjector.HalfWidth,
                        ProjectorManager.DefaultProjector.HalfHeight));

            }
            else // camera target fixed
            {
                if (workingCamera != Camera)
                {
                    workingCamera = Camera;
                }

                anglesX = amount * MathHelper.Clamp(anglesX, -speedLimit, speedLimit);
                anglesY = amount * MathHelper.Clamp(anglesY, -speedLimit, speedLimit);

                // vertical rotation
                Camera.Rotate(Camera.CameraTarget, Camera.Right, invertVertical ? -anglesY : anglesY);

                // TODO: ubah seperti camera target = forward
                if (Camera.CosineAngleBetweenCameraUpAndCameraForward < -horizontalPositiveTrackLimit)
                {
                    Camera.Rotate(Camera.CameraTarget, Camera.Right, invertVertical ? anglesY : -anglesY);
                }
                else if (Camera.CosineAngleBetweenCameraUpAndCameraForward > horizontalNegativeTrackLimit)
                {
                    Camera.Rotate(Camera.CameraTarget, Camera.Right, invertVertical ? anglesY : -anglesY);
                }

                // horizontal rotation
                Camera.Rotate(Camera.CameraTarget, Camera.CameraUp, invertHorizontal ? -anglesX : anglesX);

                Vate.SetMousePosition(
                    new Vector2(
                        ProjectorManager.DefaultProjector.HalfWidth,
                        ProjectorManager.DefaultProjector.HalfHeight));
            }
        }
        #endregion
    }
}
